package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryBrandRelationEntity;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catalog2VO;
import com.atguigu.gulimall.product.vo.Catalog3VO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        // 获取所有的菜单信息
        List<CategoryEntity> list = baseMapper.selectList(null);

        return list.stream()
                // 筛选一级菜单
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                // 在一级菜单中添加它的子菜单，使用递归方法，获取它的所有子菜单
                .map(menu -> {
                    menu.setChildren(getChildren(menu, list));
                    return menu;
                })
                // 排序
                .sorted((menu1, menu2) -> (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()))
                // 返回集合
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        // TODO 删除前检查是否被其他引用

        // 逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatalogPath(Long catalogId) {
        List<Long> paths = new ArrayList<>();

        // 递归获取所有父分类的catId
        List<Long> catalogParentPath = findCatalogParentId(catalogId, paths);
        Collections.reverse(catalogParentPath);

        return catalogParentPath.toArray(new Long[catalogParentPath.size()]);
    }

    @CacheEvict(value = "category", allEntries = true)
    @Override
    public void updateCascadeById(CategoryEntity category) {
        this.updateById(category);

        // 更新品牌分类关联表中的品牌名称
        if (StringUtils.isNotBlank(category.getName())) {
            CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
            relationEntity.setCatelogId(category.getCatId());
            relationEntity.setCatelogName(category.getName());

            categoryBrandRelationService.update(
                    relationEntity,
                    new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id", relationEntity.getCatelogId()));
        }
    }

    @Override
    @Cacheable(value = "category", key = "#root.methodName", sync = true)
    public List<CategoryEntity> getLevel1Catalog() {
        log.info("调用查询数据库方法!!!");

        return this.list(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));
    }

    @Cacheable(value = "category", key = "#root.methodName")
    @Override
    public Map<String, List<Catalog2VO>> queryCatalog() {
        // 获取所有分类
        List<CategoryEntity> list = list();
        // 将结果处理为Map
        return list.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
            // 获取二级分类
            List<CategoryEntity> level2 = queryChildrenCatalog(list, l1.getCatId());

            List<Catalog2VO> catalog2VOList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(level2)) {
                catalog2VOList = level2.stream().map(l2 -> {
                    Catalog2VO catalog2VO = new Catalog2VO();
                    catalog2VO.setCatalog1Id(l1.getCatId().toString());
                    catalog2VO.setId(l2.getCatId().toString());
                    catalog2VO.setName(l2.getName());

                    // 获取三级分类
                    List<CategoryEntity> level3 = queryChildrenCatalog(list, l2.getCatId());

                    List<Catalog3VO> catalog3VOList = new ArrayList<>();
                    if (!CollectionUtils.isEmpty(level3)) {
                        catalog3VOList = level3.stream().map(l3 -> {
                            Catalog3VO catalog3VO = new Catalog3VO();
                            catalog3VO.setCatalog2Id(l2.getCatId().toString());
                            catalog3VO.setId(l3.getCatId().toString());
                            catalog3VO.setName(l3.getName());

                            return catalog3VO;
                        }).collect(Collectors.toList());
                    }

                    catalog2VO.setCatalog3List(catalog3VOList);

                    return catalog2VO;
                }).collect(Collectors.toList());
            }

            return catalog2VOList;
        }));
    }

    public Map<String, List<Catalog2VO>> queryCatalogWithRedis() {
        // 首先从缓存中获取三级分类
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");

        // 若返回为空,则从数据库中获取放入缓存
        if (StringUtils.isEmpty(catalogJSON)) {
            log.info("缓存未命中,查询数据库......");

            Map<String, List<Catalog2VO>> map = queryCatalogByRedissonLock();

            return map;
        }
        return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});
    }

    private Map<String, List<Catalog2VO>> queryCatalogByRedissonLock() {
        // 设置分布式锁
        RLock lock = redisson.getLock("catalog-lock");
        Map<String, List<Catalog2VO>> map = null;
        try {
            lock.lock();

            map = queryCatalogIndexTreeFromDb();
        } catch (Exception e) {
            log.error(e.toString());
        } finally {
            lock.unlock();
        }

        return map;
    }

    private Map<String, List<Catalog2VO>> queryCatalogByRedisLock() {
        // 定义一个随机数
        String uuid = UUID.randomUUID().toString();
        // 通过Redis占锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.MINUTES);
        if (lock) {
            Map<String, List<Catalog2VO>> map;
            try {
                map = queryCatalogIndexTreeFromDb();
            } finally {
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }

            return map;
        } else {
            try {
                // 休眠100ms防止自旋速度过快
                Thread.sleep(100);
            } catch (Exception e) {

            }
            // 自旋重新去占锁
            return queryCatalogByRedisLock();
        }
    }

    private Map<String, List<Catalog2VO>> queryCatalogByLocalLock() {
        synchronized (this) {
            // 当线程获取到锁后,再次判断缓存中是否有数据
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (StringUtils.isNotEmpty(catalogJSON)) {
                return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
            }

            log.info("查询数据库中......");

            // 获取所有分类
            List<CategoryEntity> list = list();
            // 将结果处理为Map
            Map<String, List<Catalog2VO>> map = list.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
                // 获取二级分类
                List<CategoryEntity> level2 = queryChildrenCatalog(list, l1.getCatId());

                List<Catalog2VO> catalog2VOList = new ArrayList<>();
                if (!CollectionUtils.isEmpty(level2)) {
                    catalog2VOList = level2.stream().map(l2 -> {
                        Catalog2VO catalog2VO = new Catalog2VO();
                        catalog2VO.setCatalog1Id(l1.getCatId().toString());
                        catalog2VO.setId(l2.getCatId().toString());
                        catalog2VO.setName(l2.getName());

                        // 获取三级分类
                        List<CategoryEntity> level3 = queryChildrenCatalog(list, l2.getCatId());

                        List<Catalog3VO> catalog3VOList = new ArrayList<>();
                        if (!CollectionUtils.isEmpty(level3)) {
                            catalog3VOList = level3.stream().map(l3 -> {
                                Catalog3VO catalog3VO = new Catalog3VO();
                                catalog3VO.setCatalog2Id(l2.getCatId().toString());
                                catalog3VO.setId(l3.getCatId().toString());
                                catalog3VO.setName(l3.getName());

                                return catalog3VO;
                            }).collect(Collectors.toList());
                        }

                        catalog2VO.setCatalog3List(catalog3VOList);

                        return catalog2VO;
                    }).collect(Collectors.toList());
                }

                return catalog2VOList;
            }));

            // 在同步锁中将数据加入缓存
            String s = JSON.toJSONString(map);
            redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);

            return map;
        }
    }

    private Map<String, List<Catalog2VO>> queryCatalogIndexTreeFromDb() {
        // 当线程获取到锁后,再次判断缓存中是否有数据
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isNotEmpty(catalogJSON)) {
            return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
            });
        }

        log.info("查询数据库中......");

        // 获取所有分类
        List<CategoryEntity> list = list();
        // 将结果处理为Map
        Map<String, List<Catalog2VO>> map = list.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
            // 获取二级分类
            List<CategoryEntity> level2 = queryChildrenCatalog(list, l1.getCatId());

            List<Catalog2VO> catalog2VOList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(level2)) {
                catalog2VOList = level2.stream().map(l2 -> {
                    Catalog2VO catalog2VO = new Catalog2VO();
                    catalog2VO.setCatalog1Id(l1.getCatId().toString());
                    catalog2VO.setId(l2.getCatId().toString());
                    catalog2VO.setName(l2.getName());

                    // 获取三级分类
                    List<CategoryEntity> level3 = queryChildrenCatalog(list, l2.getCatId());

                    List<Catalog3VO> catalog3VOList = new ArrayList<>();
                    if (!CollectionUtils.isEmpty(level3)) {
                        catalog3VOList = level3.stream().map(l3 -> {
                            Catalog3VO catalog3VO = new Catalog3VO();
                            catalog3VO.setCatalog2Id(l2.getCatId().toString());
                            catalog3VO.setId(l3.getCatId().toString());
                            catalog3VO.setName(l3.getName());

                            return catalog3VO;
                        }).collect(Collectors.toList());
                    }

                    catalog2VO.setCatalog3List(catalog3VOList);

                    return catalog2VO;
                }).collect(Collectors.toList());
            }

            return catalog2VOList;
        }));

        // 在同步锁中将数据加入缓存
        String s = JSON.toJSONString(map);
        redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);

        return map;
    }

    private List<CategoryEntity> queryChildrenCatalog(List<CategoryEntity> list, Long parentCid) {
        return list.stream().filter(item -> item.getParentCid() == parentCid).collect(Collectors.toList());
    }

    private List<Long> findCatalogParentId(Long catalogId, List<Long> paths) {
        paths.add(catalogId);
        // 获取分类
        CategoryEntity category = this.getById(catalogId);

        if (category.getParentCid() != 0) {
            // 若当前节点有父节点，递归获取父节点的
            findCatalogParentId(category.getParentCid(), paths);
        }

        return paths;
    }

    private List<CategoryEntity> getChildren(CategoryEntity menu, List<CategoryEntity> list) {
        return list.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == menu.getCatId())
                .map(children -> {
                    children.setChildren(getChildren(children, list));
                    return children;
                })
                // 排序
                .sorted((menu1, menu2) -> (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()))
                // 返回集合
                .collect(Collectors.toList());
    }

}